package org.arch_learn.mvc.servlet;

import org.apache.commons.lang3.StringUtils;
import org.arch_learn.flashboot.context.GlobalContext;
import org.arch_learn.mvc.annos.Controller;
import org.arch_learn.mvc.annos.RequestMapping;
import org.arch_learn.mvc.annos.Security;
import org.arch_learn.flashboot.context.Autowired;
import org.arch_learn.mvc.auto_config.MvcProperties;
import org.arch_learn.mvc.pojo.Handler;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


public class GeneralDispatcherServlet extends HttpServlet {
    private String servletName;
    //servletContextPath
    private String servletContextPath;

    public GeneralDispatcherServlet(String servletName, String servletContextPath) {
        this.servletName = servletName;
        this.servletContextPath = servletContextPath;
    }

    @Override
    public String getServletName() {
        return servletName;
    }

    public void setServletName(String servletName) {
        this.servletName = servletName;
    }

    public String getServletContextPath() {
        return servletContextPath;
    }

    public void setServletContextPath(String servletContextPath) {
        this.servletContextPath = servletContextPath;
    }

    //加载的配置信息
    private Properties properties = new Properties();

    //缓存到扫描的全限定类名
    private Set<String> classNames = new HashSet<>();

    //mvc单例池
    private HashMap<String, Object> mvcSingletonPool = new HashMap<>();

    //handlers列表
    private List<Handler> handlers = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Handler handler = getHandler(req, resp);
        if (handler == null) return;

        //参数绑定
        //获取所有参数类型数组，这个数组的长度就是需要传入的args数组的长度
        Parameter[] parameters = handler.getMethod().getParameters();
        //根据形参数组长度创建实参数组，传入反射调用
        Object[] args = new Object[parameters.length];
        //给实参数组塞值，必须保证实参的顺序和形参顺序一致
        Map<String, String[]> parameterMap = req.getParameterMap();
        //遍历得到所有req中的参数
        parameterMap.entrySet().forEach(paramEntry -> {
            String[] values = paramEntry.getValue();
            String valuesStr = StringUtils.join(values, ",");
            //如果参数和方法中的参数匹配上了，填充数据
            if (handler.getParamIndexMapping().containsKey(paramEntry.getKey())) {
                Integer index = handler.getParamIndexMapping().get(paramEntry.getKey());
                args[index] = valuesStr;
            }
        });

        Integer httpServletRequestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        if (httpServletRequestIndex != null) {
            args[httpServletRequestIndex] = req;
        }
        Integer httpServletResponseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        if (httpServletResponseIndex != null) {
            args[httpServletResponseIndex] = resp;
        }
        try {
            handler.getMethod().invoke(handler.getControllerObj(), args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private void checkAuth(Handler handler, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Method method = handler.getMethod();
        Security annotation = method.getAnnotation(Security.class);
        if (null != annotation) {
            String[] names = annotation.names();
            Map<String, String[]> parameterMap = req.getParameterMap();
            String[] namesValue = parameterMap.get("name");
            String namesStr = StringUtils.join(namesValue, ",");
            if (Arrays.stream(names).noneMatch(v -> v.equalsIgnoreCase(namesStr))) {
                resp.setStatus(403);
                resp.getWriter().write("无权限访问");
            }
        }
    }

    private Handler getHandler(HttpServletRequest req, HttpServletResponse resp) {
        if (handlers.isEmpty()) return null;
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        String servletPath = req.getServletPath();
        String temp = requestURI
                .replace(contextPath, "")
                .replace(servletPath, "");
        String substring = temp.substring(0, temp.indexOf("?"));
        String finalRequestURI = substring;
        List<Handler> matchesHandlers = handlers.stream()
                .filter(v -> v.getPattern().matcher(finalRequestURI).matches())
                .collect(Collectors.toList());
        if (matchesHandlers.size() > 1) throw new RuntimeException("多个handler匹配url");
        if (matchesHandlers.size() == 0) {
            resp.setStatus(404);
            return null;
        } else {
            return matchesHandlers.get(0);
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件 springmvc.properties
//        String mvcContextConfigLocation = config.getInitParameter("contextConfigLocation");
//        doLoadConfig(mvcContextConfigLocation);

        //2.扫描相关类与注解
//        doScan(properties.getProperty("scanPackage"));

//        //3.初始化bean，实现ioc子容器
//        doInstance();
//
//        //4.维护对象依赖关系实现依赖注入
//        doAutoWired();
//
//        //5.初始化mvc组件，构造handlerMapping处理器映射器，将配置好的url和method建立映射关系
//        initHandlerMappings();
//
//        //等待请求j进入，处理请求
//        System.out.println("自定义mvc初始化完成...");
    }

    public void init() {
        //2.扫描相关类与注解
        doScan(GlobalContext.getStarterPackage());
        //3.初始化bean，实现ioc子容器
        doInstance();

        //4.维护对象依赖关系实现依赖注入
        doAutoWired();

        //5.初始化mvc组件，构造handlerMapping处理器映射器，将配置好的url和method建立映射关系
        initHandlerMappings();

        //等待请求j进入，处理请求
        System.out.println("自定义mvc初始化完成...");
    }

    private void initHandlerMappings() {
        if (mvcSingletonPool.isEmpty()) return;
        mvcSingletonPool.entrySet().forEach(entry -> {
            Class<?> clazz = entry.getValue().getClass();
            if (clazz.isAnnotationPresent(Controller.class)) {
                String baseUrl = "";
                if (clazz.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                    baseUrl = requestMapping.value();
                }
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                        String methodUrl = annotation.value();
                        String url = baseUrl + methodUrl;
                        Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));
                        Parameter[] parameters = method.getParameters();
                        for (int i = 0; i < parameters.length; i++) {
                            Parameter parameter = parameters[i];
                            //参数中的HttpServletRequest和HttpServletResponse做特殊处理
                            if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                                handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                            } else {
                                handler.getParamIndexMapping().put(parameter.getName(), i);
                            }
                        }
                        handlers.add(handler);
                    }
                }
            }
        });
    }

    //依赖注入。主要做的事情是将被@Service标注的业务类的代理对象注入到controller中
    private void doAutoWired() {
        if (this.mvcSingletonPool.size() == 0) return;
        Map<String,Object> globalSingletonPool = GlobalContext.globalSingletonPool;
        mvcSingletonPool.entrySet().forEach(entry -> {
            Object obj = entry.getValue();
            Field[] fields = obj.getClass().getDeclaredFields();
            Arrays.stream(fields).forEach(f -> {
                if (f.isAnnotationPresent(Autowired.class)) {
                    Autowired annotation = f.getAnnotation(Autowired.class);
                    String value = annotation.value();
                    Class<?> clazz = f.getType();
                    String name = clazz.getName();
                    String key = value.length() == 0 ? name : value;
                    Object o = mvcSingletonPool.get(key) == null ? globalSingletonPool.get(key) : mvcSingletonPool.get(key);
                    if (o != null) {
                        f.setAccessible(true);
                        try {
                            f.set(obj, o);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        });
    }

    //基于classNames缓存的全限定类名,以及反射技术，完成controller层对象的创建和管理
    private void doInstance() {
        Map<String, Object> globalSingletonPool = GlobalContext.globalSingletonPool;
        if (classNames.size() == 0) return;
        classNames.forEach(v -> {
            try {
                Class<?> clazz = Class.forName(v);
                if (clazz.isAnnotationPresent(Controller.class)) {
                    Controller annotation = clazz.getAnnotation(Controller.class);
                    String key = annotation.value().length() == 0 ? clazz.getName() : annotation.value();
                    Object o = clazz.newInstance();
                    mvcSingletonPool.putIfAbsent(key, o);
                    globalSingletonPool.putIfAbsent(key, o);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
    }

    private void doScan(String scanPackage) {
        String scanPath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");

        File root = new File(scanPath);
        File[] files = root.listFiles();
        for (File f : files) {
            if (f.isDirectory()) {
                doScan(scanPackage + "." + f.getName());
            } else if (f.getName().endsWith(".class")) {
                String className = scanPackage + "." + f.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

//    private void doLoadConfig(String mvcContextConfigLocation) {
//        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(mvcContextConfigLocation);
//        try {
//            properties.load(resourceAsStream);
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }


    @Override
    public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        MvcProperties mvcProperties = GlobalContext.getInstance(MvcProperties.class);
        if (mvcProperties.getLoadOnStartUp() == 0) this.init();
        String servletName = this.getServletName();
        System.out.println(servletName + "处理了请求url为" + req.getRequestURI() + "的请求---");
        String method = req.getMethod();
        System.out.println("---请求方法为：" + method);
        switch (method) {
            case "GET":
                doPost(req, res);
                break;
            case "POST":
                doPost(req, res);
                break;
            case "PUT":
                doPost(req, res);
                break;
            case "DELETE":
                doPost(req, res);
                break;
        }
    }
}
